#include "sdk.h"
#include "incremental_select_helper.h"

IncrementalSelectIterator::~IncrementalSelectIterator() {
}

int IncrementalSelectIterator::GetColumnWidth( int column ) const {
  return wxLIST_AUTOSIZE;
}

void IncrementalSelectIterator::CalcColumnWidth( M_Code_unused wxListCtrl &list ) {
}

int IncrementalSelectIteratorIndexed::GetFilteredCount() const {
  return m_indices.size();
}

void IncrementalSelectIteratorIndexed::Reset() {
  m_indices.clear();
}

void IncrementalSelectIteratorIndexed::AddIndex( int index ) {
  m_indices.push_back( index );
}

int IncrementalSelectIteratorIndexed::GetUnfilteredIndex( int index ) const {
  if( index >= 0 && index < int( m_indices.size() ) ) {
    return m_indices[index];
  } else {
    return wxNOT_FOUND;
  }
}

IncrementalSelectHandler::IncrementalSelectHandler( wxDialog* parent, IncrementalSelectIterator *iterator ) :
  m_parent( parent ),
  m_list( nullptr ),
  m_text( nullptr ),
  m_iterator( iterator ) {
  m_parent->PushEventHandler( this );
  SetEvtHandlerEnabled( true );
}

IncrementalSelectHandler::~IncrementalSelectHandler() {
}

void IncrementalSelectHandler::Init( wxListCtrl *list, wxTextCtrl *text ) {
  m_list = list;
  m_text = text;
  m_text->Connect( wxEVT_COMMAND_TEXT_UPDATED, ( wxObjectEventFunction )&IncrementalSelectHandler::OnTextChanged,
                   nullptr, this );
  m_text->Connect( wxEVT_KEY_DOWN, ( wxObjectEventFunction )&IncrementalSelectHandler::OnKeyDown, nullptr, this );
  m_list->Connect( wxEVT_KEY_DOWN, ( wxObjectEventFunction )&IncrementalSelectHandler::OnKeyDown, nullptr, this );
  m_list->Connect( wxEVT_COMMAND_LIST_ITEM_ACTIVATED, wxListEventHandler( IncrementalSelectHandler::OnItemActivated ),
                   nullptr, this );
  m_iterator->CalcColumnWidth( *m_list );
  FilterItems();
}

void IncrementalSelectHandler::DeInit( wxWindow *window ) {
  m_list->Disconnect( wxEVT_COMMAND_LIST_ITEM_ACTIVATED, wxListEventHandler( IncrementalSelectHandler::OnItemActivated ),
                      nullptr, this );
  m_text->Disconnect( wxEVT_KEY_DOWN, ( wxObjectEventFunction )&IncrementalSelectHandler::OnKeyDown, nullptr, this );
  m_list->Disconnect( wxEVT_KEY_DOWN, ( wxObjectEventFunction )&IncrementalSelectHandler::OnKeyDown, nullptr, this );
  m_text->Disconnect( wxEVT_COMMAND_TEXT_UPDATED, ( wxObjectEventFunction )&IncrementalSelectHandler::OnTextChanged,
                      nullptr, this );
  SetEvtHandlerEnabled( false );
  window->RemoveEventHandler( this );
}

void IncrementalSelectHandler::OnTextChanged( wxCommandEvent& event ) {
  FilterItems();
  event.Skip();
}

void FilterItemsFinalize( wxListCtrl &list, IncrementalSelectIterator &iterator ) {
  list.SetItemCount( iterator.GetFilteredCount() );
  for( int ii = 0; ii < list.GetColumnCount(); ++ii ) {
    int width = iterator.GetColumnWidth( ii );
    if( width != -1 ) {
      list.SetColumnWidth( ii, width );
    }
  }
  if( iterator.GetFilteredCount() > 0 ) {
    list.SetItemState( 0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED );
  }
  list.Refresh();
}

void IncrementalSelectHandler::FilterItems() {
  m_iterator->Reset();
  const wxString &inputPattern = m_text->GetValue().Lower();
  if( inputPattern.empty() ) {
    int count = m_iterator->GetTotalCount();
    for( int ii = 0; ii < count; ++ii ) {
      m_iterator->AddIndex( ii );
    }
    FilterItemsFinalize( *m_list, *m_iterator );
    return;
  }
  wxString search( _T( "*" ) + inputPattern + _T( "*" ) );
  bool isWord = !inputPattern.empty();
  for( auto ch : inputPattern ) {
    if( !wxIsalpha( ch ) ) {
      isWord = false;
      break;
    }
  }
  std::vector<int> indices, promoted;
  indices.reserve( 100 );
  promoted.reserve( 100 );
  for( int i = 0; i < m_iterator->GetTotalCount(); ++i ) {
    wxString const &item = m_iterator->GetItemFilterString( i ).Lower();
    if( item.Matches( search.c_str() ) ) {
      if( isWord ) {
        size_t pos = 0, newPos;
        bool isPromoted = false;
        while( ( newPos = item.find( inputPattern, pos ) ) != wxString::npos ) {
          if( newPos == 0 ) {
            isPromoted = true;
            break;
          }
          if( !wxIsalpha( item[newPos - 1] ) ) {
            isPromoted = true;
            break;
          }
          pos = newPos + 1;
        }
        if( isPromoted ) {
          promoted.push_back( i );
        } else
        { indices.push_back( i ); }
      } else
      { indices.push_back( i ); }
    }
  }
  for( auto i : promoted ) {
    m_iterator->AddIndex( i );
  }
  for( auto i : indices ) {
    m_iterator->AddIndex( i );
  }
  FilterItemsFinalize( *m_list, *m_iterator );
}

typedef int wxStandardID;

static wxStandardID KeyDownAction( wxKeyEvent& event, int &selected, int selectedMax ) {
  switch( event.GetKeyCode() ) {
    case WXK_RETURN:
    case WXK_NUMPAD_ENTER:
      return wxID_OK;
    case WXK_ESCAPE:
      return wxID_CANCEL;
    case WXK_UP:
    case WXK_NUMPAD_UP:
      if( selected ) {
        selected--;
      }
      break;
    case WXK_DOWN:
    case WXK_NUMPAD_DOWN:
      selected++;
      break;
    case WXK_PAGEUP:
    case WXK_NUMPAD_PAGEUP:
      selected -= 10;
      break;
    case WXK_PAGEDOWN:
    case WXK_NUMPAD_PAGEDOWN:
      selected += 10;
      break;
    case WXK_HOME:
      if( wxGetKeyState( WXK_CONTROL ) ) {
        selected = 0;
      } else
      { event.Skip(); }
      break;
    case WXK_END:
      if( wxGetKeyState( WXK_CONTROL ) ) {
        selected = selectedMax;
      } else
      { event.Skip(); }
      break;
    default:
      event.Skip();
      break;
  }
  if( selected < 0 ) {
    selected = 0;
  } else if( selected > selectedMax ) {
    selected = selectedMax;
  }
  return wxID_LOWEST;
}

void IncrementalSelectHandler::OnKeyDown( wxKeyEvent& event ) {
  int selected = m_list->GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
  int selectedMax = m_list->GetItemCount() - 1;
  wxStandardID result = KeyDownAction( event, selected, selectedMax );
  if( result != wxID_LOWEST ) {
    m_parent->EndModal( result );
  } else if( selectedMax >= 0 ) {
    m_list->SetItemState( selected, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED );
    m_list->EnsureVisible( selected );
  }
}

void IncrementalSelectHandler::OnItemActivated( wxListEvent &event ) {
  m_parent->EndModal( wxID_OK );
}

int IncrementalSelectHandler::GetSelection() {
  int index = m_list->GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
  if( index == -1 ) {
    return wxNOT_FOUND;
  } else {
    return m_iterator->GetUnfilteredIndex( index );
  }
}

IncrementalListCtrl::IncrementalListCtrl( wxWindow *parent, wxWindowID winid, const wxPoint& pos, const wxSize& size,
    long style, const wxValidator &validator, const wxString &name ) :
  wxListCtrl( parent, winid, pos, size, style, validator, name ) {
}

wxString IncrementalListCtrl::OnGetItemText( long item, long column ) const {
  return m_Iterator->GetDisplayText( item, column );
}

void IncrementalListCtrl::SetIterator( IncrementalSelectIterator *iterator ) {
  m_Iterator = iterator;
}


IncrementalSelectArrayIterator::IncrementalSelectArrayIterator( const wxArrayString &items ) :
  m_items( items ), m_columnWidth( 300 ) {
}

int IncrementalSelectArrayIterator::GetTotalCount() const {
  return m_items.size();
}
const wxString& IncrementalSelectArrayIterator::GetItemFilterString( int index ) const {
  return m_items[index];
}
wxString IncrementalSelectArrayIterator::GetDisplayText( int index, int column ) const {
  return m_items[m_indices[index]];
}
int IncrementalSelectArrayIterator::GetColumnWidth( int column ) const {
  return m_columnWidth;
}

void IncrementalSelectArrayIterator::CalcColumnWidth( wxListCtrl &list ) {
  int length = 0;
  wxString longest;
  for( const auto &item : m_items ) {
    int itemLength = item.length();
    if( length < itemLength ) {
      longest = item;
      length = itemLength;
    }
  }
  if( length > 0 ) {
    int yTemp;
    list.GetTextExtent( longest, &m_columnWidth, &yTemp );
    m_columnWidth += 50;
  } else {
    m_columnWidth = 300;
  }
}

const long ID_TEXTCTRL1 = wxNewId();
const long ID_RESULT_LIST = wxNewId();

BEGIN_EVENT_TABLE( IncrementalSelectDialog, wxDialog )
END_EVENT_TABLE()

IncrementalSelectDialog::IncrementalSelectDialog( wxWindow* parent, IncrementalSelectIterator *iterator,
    const wxString &title, const wxString &message ) :
  m_handler( this, iterator ) {
  BuildContent( parent, iterator, title, message );
  m_handler.Init( m_resultList, m_text );
}

IncrementalSelectDialog::~IncrementalSelectDialog() {
  m_handler.DeInit( this );
}

void IncrementalSelectDialog::BuildContent( wxWindow* parent, IncrementalSelectIterator *iterator, const wxString &title,
    const wxString &message ) {
  Create( parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize,
          wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxCLOSE_BOX | wxMAXIMIZE_BOX, _T( "wxID_ANY" ) );
  wxBoxSizer *sizer = new wxBoxSizer( wxVERTICAL );
  wxStaticText *labelCtrl = new wxStaticText( this, wxID_ANY, message, wxDefaultPosition, wxDefaultSize, 0,
      _T( "wxID_ANY" ) );
  sizer->Add( labelCtrl, 0, wxTOP | wxLEFT | wxRIGHT | wxEXPAND, 5 );
  m_text = new wxTextCtrl( this, ID_TEXTCTRL1, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator,
                           _T( "ID_TEXTCTRL1" ) );
  m_text->SetFocus();
  sizer->Add( m_text, 0, wxTOP | wxLEFT | wxRIGHT | wxEXPAND, 5 );
  m_resultList = new IncrementalListCtrl( this, ID_RESULT_LIST, wxDefaultPosition, wxDefaultSize,
                                          wxLC_REPORT | wxLC_NO_HEADER | wxLC_SINGLE_SEL | wxLC_VIRTUAL | wxVSCROLL | wxHSCROLL,
                                          wxDefaultValidator, _T( "ID_RESULT_LIST" ) );
  m_resultList->SetMinSize( wxSize( 500, 300 ) );
  sizer->Add( m_resultList, 1, wxALL | wxEXPAND, 5 );
  SetSizer( sizer );
  sizer->Fit( this );
  sizer->SetSizeHints( this );
  wxListItem column;
  column.SetId( 0 );
  column.SetText( _T( "Column" ) );
  column.SetWidth( 300 );
  m_resultList->InsertColumn( 0, column );
  m_resultList->SetIterator( iterator );
}

int IncrementalSelectDialog::GetSelection() {
  return m_handler.GetSelection();
}
